// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包cfg保存由go命令的多个部分共享的配置。
package cfg

import (
	"bytes"
	"fmt"
	"go/build"
	"internal/buildcfg"
	"internal/cfg"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"

	"cmd/go/internal/fsys"
)

// 这些是build和其他命令使用的通用“build标志”。
var (
	BuildA                 bool   // -a标志
	BuildBuildmode         string // -buildmode标志
	BuildBuildvcs          bool   // -buildvcs标志
	BuildContext           = defaultContext()
	BuildMod               string                  // -mod标志
	BuildModExplicit       bool                    // ，如果默认设置
	BuildI                 bool                    // /-i标志
	BuildLinkshared        bool                    // /-linkshared标志
	BuildMSan              bool                    // /-msan标志
	BuildASan              bool                    // /-asan标志
	BuildN                 bool                    // /-n标志
	BuildO                 string                  // /-o标志
	BuildP                 = runtime.GOMAXPROCS(0) // /-p标志
	BuildToolchainName     string
	BuildToolchainCompiler func() string
	BuildToolchainLinker   func() string
	BuildWork              bool // -工作标志
	BuildX                 bool // /-x标志

	ModCacheRW bool   // /-modcacherw标志
	ModFile    string // /-modfile标志

	CmdName string // /“构建”、“安装”、“列表”、“mod tidy”等。

	DebugActiongraph string // /-调试操作图标志（未记录、不稳定）

	// /-GOPATH在未设置GOPATH时设置错误。它包含一个
	// 解释为什么GOPATH未设置。bao dong
	GoPathError string

	GOEXPERIMENT = envOr("GOEXPERIMENT", buildcfg.DefaultGOEXPERIMENT)
)

func defaultContext() build.Context {
	ctxt := build.Default


	ctxt.GOROOT = findGOROOT()
	if runtime.Compiler != "gccgo" {
		// 因为工具目录不会根据环境
		// 变量移动。这与
		// go/build中ToolDir的初始化相匹配，除了使用ctxt。GOROOT而不是
		// 运行时。戈鲁特。
		build.ToolDir = filepath.Join(ctxt.GOROOT, "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH)
	}

	ctxt.GOPATH = envOr("GOPATH", gopath(ctxt))

	// 使用默认值覆盖go/build中计算的默认值
	// 如果已知，从go环境配置文件。
	ctxt.GOOS = envOr("GOOS", ctxt.GOOS)
	ctxt.GOARCH = envOr("GOARCH", ctxt.GOARCH)

	// 实验标志基于GOARCH，因此它们可能需要更改。TODO:这个应该清理一下。
	buildcfg.UpdateExperiments(ctxt.GOOS, ctxt.GOARCH, GOEXPERIMENT)
	ctxt.ToolTags = nil
	for _, exp := range buildcfg.EnabledExperiments() {
		ctxt.ToolTags = append(ctxt.ToolTags, "goexperiment."+exp)
	}

	// 是否启用cgo的go/build规则是：
	// 1。如果设置了$CGO_ENABLED，请尊重它。
	// 2。否则，如果这是交叉编译，请禁用cgo。
	// 3。否则，对GOOS/GOARCH使用内置默认值。
	// 使用新的GOOS/GOARCH设置在此处重新创建该逻辑。
	if v := Getenv("CGO_ENABLED"); v == "0" || v == "1" {
		ctxt.CgoEnabled = v[0] == '1'
	} else if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH {
		ctxt.CgoEnabled = false
	} else {
		// 对GOOS/GOARCH使用内置默认cgo设置。注意ctxt。GOOS/GOARCH源于首选项列表
		// （1）环境，（2）go/env文件，（3）运行时常量，
		// while go/build。违约GOOS/GOARCH来自首选项列表
		// /（1）环境，（2）运行时常量。我们知道ctxt。GOOS/GOARCH==运行时。GOOS/GOARCH；
		// 不管这是怎么发生的，去建造吧。默认设置将使
		// 做出相同的决定（要么显式设置环境变量
		// 以匹配运行时常量，要么未设置环境变量，其中
		// case go/build返回到运行时常量），因此
		// go/build。违约GOOS/GOARCH==运行时。GOOS/GOARCH。
		// So ctxt。CgoEnabled（=go/build.Default.CgoEnabled）是正确的
		// 原样，可以不修改。
		// 这里没什么可做的。
	}

	ctxt.OpenFile = func(path string) (io.ReadCloser, error) {
		return fsys.Open(path)
	}
	ctxt.ReadDir = fsys.ReadDir
	ctxt.IsDir = func(path string) bool {
		isDir, err := fsys.IsDir(path)
		return err == nil && isDir
	}

	return ctxt
}

func init() {
	BuildToolchainCompiler = func() string { return "missing-compiler" }
	BuildToolchainLinker = func() string { return "missing-linker" }
}

// EnvVar是一个环境变量Name=Value。
type EnvVar struct {
	Name  string
	Value string
}

// OrigEnv是程序启动时的原始环境。
var OrigEnv []string

// CmdEnv是运行go tool命令的新环境。
// 用户二进制文件（在go测试或go运行期间）使用OrigEnv运行，
// 非CmdEnv。
var CmdEnv []EnvVar

// 全局构建参数（在包加载期间使用）
var (
	Goarch = BuildContext.GOARCH
	Goos   = BuildContext.GOOS

	ExeSuffix = exeSuffix()

	// ModulesEnabled指定go命令是否在模块感知模式下运行
	// 与GOPATH模式相反。
	// 等于modload。已启用，但并非所有包都可以导入modload。
	ModulesEnabled bool
)

func exeSuffix() string {
	if Goos == "windows" {
		return ".exe"
	}
	return ""
}

var envCache struct {
	once sync.Once
	m    map[string]string
}

// EnvFile返回Go环境配置文件的名称。
func EnvFile() (string, error) {
	if file := os.Getenv("GOENV"); file != "" {
		if file == "off" {
			return "", fmt.Errorf("GOENV=off")
		}
		return file, nil
	}
	dir, err := os.UserConfigDir()
	if err != nil {
		return "", err
	}
	if dir == "" {
		return "", fmt.Errorf("missing user-config dir")
	}
	return filepath.Join(dir, "go/env"), nil
}

func initEnvCache() {
	envCache.m = make(map[string]string)
	file, _ := EnvFile()
	if file == "" {
		return
	}
	data, err := os.ReadFile(file)
	if err != nil {
		return
	}

	for len(data) > 0 {
		// 接下一行。
		line := data
		i := bytes.IndexByte(data, '\n')
		if i >= 0 {
			line, data = line[:i], data[i+1:]
		} else {
			data = nil
		}

		i = bytes.IndexByte(line, '=')
		if i < 0 || line[0] < 'A' || 'Z' < line[0] {
			// 行缺少=（或空）或注释或不是有效的环境名称。忽视
			// （这种情况不应该发生，因为该文件应该由“go env-w”专门维护，几乎是
			// ，但最好是静默忽略，而不是仅仅因为env文件已损坏而使
			// go命令不可用。）
			continue
		}
		key, val := line[:i], line[i+1:]
		envCache.m[string(key)] = string(val)
	}
}

// Getenv获取配置键的值。
// 它先查阅操作系统环境
// 然后查阅go/env文件。
// 如果为无法设置的密钥调用Getenv
// 在go/env文件（例如GODEBUG）中，它会惊慌失措。
// 这确保了CanGetenv的准确性，因此
// /'go env-w'与Getenv可以检索的内容保持同步。
func Getenv(key string) string {
	if !CanGetenv(key) {
		switch key {
		case "CGO_TEST_ALLOW", "CGO_TEST_DISALLOW", "CGO_test_ALLOW", "CGO_test_DISALLOW":
			// 用于内部/工作/安全测试。去allow 
		default:
			panic("internal error: invalid Getenv " + key)
		}
	}
	val := os.Getenv(key)
	if val != "" {
		return val
	}
	envCache.once.Do(initEnvCache)
	return envCache.m[key]
}

// CanGetenv报告密钥是否为有效的go/env配置密钥。
func CanGetenv(key string) bool {
	return strings.Contains(cfg.KnownEnv, "\t"+key+"\n")
}

var (
	GOROOT       = BuildContext.GOROOT
	GOBIN        = Getenv("GOBIN")
	GOROOTbin    = filepath.Join(GOROOT, "bin")
	GOROOTpkg    = filepath.Join(GOROOT, "pkg")
	GOROOTsrc    = filepath.Join(GOROOT, "src")
	GOROOT_FINAL = findGOROOT_FINAL()
	GOMODCACHE   = envOr("GOMODCACHE", gopathDir("pkg/mod"))

	// 用于envcmd。MkEnv和build ID计算。
	GOARM    = envOr("GOARM", fmt.Sprint(buildcfg.GOARM))
	GO386    = envOr("GO386", buildcfg.GO386)
	GOAMD64  = envOr("GOAMD64", fmt.Sprintf("%s%d", "v", buildcfg.GOAMD64))
	GOMIPS   = envOr("GOMIPS", buildcfg.GOMIPS)
	GOMIPS64 = envOr("GOMIPS64", buildcfg.GOMIPS64)
	GOPPC64  = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", buildcfg.GOPPC64))
	GOWASM   = envOr("GOWASM", fmt.Sprint(buildcfg.GOWASM))

	GOPROXY    = envOr("GOPROXY", "https:// 代理。戈朗。组织，直接“）
	GOSUMDB    = envOr("GOSUMDB", "sum.golang.org")
	GOPRIVATE  = Getenv("GOPRIVATE")
	GONOPROXY  = envOr("GONOPROXY", GOPRIVATE)
	GONOSUMDB  = envOr("GONOSUMDB", GOPRIVATE)
	GOINSECURE = Getenv("GOINSECURE")
	GOVCS      = Getenv("GOVCS")
)

var SumdbDir = gopathDir("pkg/sumdb")

// GetArchEnv返回特定于GOARCH的架构环境变量的名称和设置。
// 如果当前架构没有特定于GOARCH的变量，
// GetArchEnv返回空键和值。
func GetArchEnv() (key, val string) {
	switch Goarch {
	case "arm":
		return "GOARM", GOARM
	case "386":
		return "GO386", GO386
	case "amd64":
		return "GOAMD64", GOAMD64
	case "mips", "mipsle":
		return "GOMIPS", GOMIPS
	case "mips64", "mips64le":
		return "GOMIPS64", GOMIPS64
	case "ppc64", "ppc64le":
		return "GOPPC64", GOPPC64
	case "wasm":
		return "GOWASM", GOWASM
	}
	return "", ""
}

// envOr返回Getenv（键）如果已设置，则为def。
func envOr(key, def string) string {
	val := Getenv(key)
	if val == "" {
		val = def
	}
	return val
}

// 在
// x/tools/cmd/godoc/goroot中有findGOROOT、isSameDir和isGOROOT的副本。去
// 暂时保持同步。

// findGOROOT使用显式
// 提供的环境变量（包含当前
// 操作系统的GOROOT）返回GOROOT值。可执行值，或者二进制文件从运行时开始使用
// 构建的GOROOT。GOROOT（）。
// 
// x/tools/cmd/godoc/goroot中有此代码的副本。去
func findGOROOT() string {
	if env := Getenv("GOROOT"); env != "" {
		return filepath.Clean(env)
	}
	def := filepath.Clean(runtime.GOROOT())
	if runtime.Compiler == "gccgo" {
		// gccgo没有真正的GOROOT，当然也不依赖于可执行文件的位置。
		return def
	}
	exe, err := os.Executable()
	if err == nil {
		exe, err = filepath.Abs(exe)
		if err == nil {
			if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
				// 如果def（runtime.GOROOT（））和dir是同一个
				// 目录，则首选def中使用的拼写。
				if isSameDir(def, dir) {
					return def
				}
				return dir
			}
			exe, err = filepath.EvalSymlinks(exe)
			if err == nil {
				if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
					if isSameDir(def, dir) {
						return def
					}
					return dir
				}
			}
		}
	}
	return def
}

func findGOROOT_FINAL() string {
	// /$GOROOT_FINAL仅在制作期间使用。bash 
	// 所以它不能用go/env设置，所以我们使用os。这里是Getenv。
	def := GOROOT
	if env := os.Getenv("GOROOT_FINAL"); env != "" {
		def = filepath.Clean(env)
	}
	return def
}

// isSameDir报告dir1和dir2是否为同一目录。
func isSameDir(dir1, dir2 string) bool {
	if dir1 == dir2 {
		return true
	}
	info1, err1 := os.Stat(dir1)
	info2, err2 := os.Stat(dir2)
	return err1 == nil && err2 == nil && os.SameFile(info1, info2)
}

// isGOROOT报告路径是否看起来像GOROOT。
// 
// 它通过查找路径/pkg/tool目录来实现这一点，
// 这是对cmd/go工具进行有用操作所必需的，
// 通常不存在于GOPATH中。
// 
// x/tools/cmd/godoc/goroot中有此代码的副本。去
func isGOROOT(path string) bool {
	stat, err := os.Stat(filepath.Join(path, "pkg", "tool"))
	if err != nil {
		return false
	}
	return stat.IsDir()
}

func gopathDir(rel string) string {
	list := filepath.SplitList(BuildContext.GOPATH)
	if len(list) == 0 || list[0] == "" {
		return ""
	}
	return filepath.Join(list[0], rel)
}

func gopath(ctxt build.Context) string {
	if len(ctxt.GOPATH) > 0 {
		return ctxt.GOPATH
	}
	env := "HOME"
	if runtime.GOOS == "windows" {
		env = "USERPROFILE"
	} else if runtime.GOOS == "plan9" {
		env = "home"
	}
	if home := os.Getenv(env); home != "" {
		def := filepath.Join(home, "go")
		if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) {
			GoPathError = "cannot set GOROOT as GOPATH"
		}
		return ""
	}
	GoPathError = fmt.Sprintf("%s is not set", env)
	return ""
}
